SpringCloud之Feign,OpenFeign和Feign拦截器讲解

您所在的位置:网站首页 feign拦截器 accesstoken SpringCloud之Feign,OpenFeign和Feign拦截器讲解

SpringCloud之Feign,OpenFeign和Feign拦截器讲解

#SpringCloud之Feign,OpenFeign和Feign拦截器讲解| 来源: 网络整理| 查看: 265

目录1 Feign1.1 定义1.1.1 简介1.1.2 OpenFeign和Feign的区别1.1.3 属性介绍1.1.4 原理解析1.1.5 负载均衡策略1.1.6 饥饿加载1.2 OpenFeign准备工作1.2.1 引入依赖1.2.2 启动类和yml文件1.3 使用OpenFeign1.3.1 简单使用@FeignClient1.3.2 @RequestLine1.4 OpenFeign添加header信息1.4.1 在@RequestMapping注解里添加headers属性1.4.2 在方法参数前面添加@RequestHeader注解1.4.3 在方法或者类上添加@Headers的注解1.4.4 在方法参数前面添加@HeaderMap注解1.4.5 实现RequestInterceptor接口1.5 OpenFeign日志打印1.6 手动创建 Feign 客户端1.7 Feign配置1.7.1 使用OKhttp1.7.2 开启GZIP压缩2 Feign拦截器RequestInterceptor2.1 定义2.2 RequestInterceptor2.3 配置RequestInterceptor2.3.1 通过@EnableFeignClients的defaultConfiguration属性配置2.3.2 通过@FeignClient的configuration属性配置2.3.3 通过配置文件配置

1 Feign 1.1 定义 1.1.1 简介

Feign是一个声明式的Web Service客户端,通过声明RESTful请求客户端 Spring Cloud集成了Ribbon和Eureka,可在使用Feign时提供负载均衡的http客户端

Java当中常见的Http客户端有很多,除了Feign,类似的还有Apache 的 HttpClient 以及OKHttp3,还有SpringBoot自带的RestTemplate这些都是Java当中常用的HTTP 请求工具

微服务直接调用使用RestTemplate进行远程调用,非常方便,那么有了RestTemplate为什么还要有Feign,因为RestTemplate有一个致命的问题:硬编码。 点击了解Spring之RestTemplate 在 RestTemplate 调用中,我们每个调用远程接口的方法,都将远程接口对应的 ip、端口,或 service-id 硬编码到了 URL 中,如果远程接口的 ip、端口、service-id 有修改的话,需要将所有的调用都修改一遍,这样难免会出现漏改、错改等问题,且代码不便于维护。为了解决这个问题,Netflix 推出了 Feign 来统一管理远程调用

1.1.2 OpenFeign和Feign的区别

Feign:

Feign是Spring Cloud组件中的一个轻量级RESTful的HTTP服务客户端,Feign内置了Ribbon,用来做客户端负载均衡,去调用服务注册中心的服务。Feign的使用方式是:使用Feign的注解定义接口,调用这个接口,就可以调用服务注册中心的服务。

org.springframework.cloud spring-cloud-starter-feign

OpenFeign:

OpenFeign是Spring Cloud在Feign的基础上支持了SpringMVC的注解,如@RequesMapping等等。OpenFeign的@FeignClient可以解析SpringMVC的@RequestMapping注解下的接口,并通过动态代理的方式产生实现类,实现类中做负载均衡并调用其他服务。

org.springframework.cloud spring-cloud-starter-openfeign

Feign是在2019就已经不再更新了,通过maven网站就可以看出来,随之取代的是OpenFeign,从名字上就可以知道,它是Feign的升级版

1.1.3 属性介绍

@FeignClient标签的常用属性如下:

name:指定FeignClient的名称,如果项目使用了Ribbon,name属性会作为微服务的名称,用于服务发现 value: 调用服务名称,和name属性相同 url: url一般用于调试,可以手动指定@FeignClient调用的地址 decode404: 当发生http 404错误时,如果该字段位true,会调用decoder进行解码,否则抛出FeignException configuration: Feign配置类,可以自定义Feign的Encoder、Decoder、LogLevel、Contract fallback: 定义容错的处理类,当调用远程接口失败或超时时,会调用对应接口的容错逻辑,fallback指定的类必须实现@FeignClient标记的接口 熔断机制,调用失败时,走的一些回退方法,可以用来抛出异常或给出默认返回数据。底层依赖hystrix,启动类要加上@EnableHystrix fallbackFactory: 工厂类,用于生成fallback类示例,通过这个属性我们可以实现每个接口通用的容错逻辑,减少重复的代码 另外,fallbackFactory 可以捕获异常信息并返回默认降级结果,可以打印堆栈信息,但是fallback不能打印堆栈信息,不利于问题排查 path: 定义当前FeignClient的统一前缀 primary: 默认值true,Feign会自动生成FeignClient,也就是接口的jdk代理实现

注意:name/value和url同时存在生效问题

name/value属性:这两个的作用是一样的,指定的是调用服务的微服务名称 url:指定调用服务的全路径,经常用于本地测试 如果同时指定name和url属性,则以url属性为准,name属性指定的值便当做客户端的名称 1.1.4 原理解析

Feign调用步骤:

程序启动时,扫描所有的@FeignClient注解 当接口方法被调用时,通过JDK代理来生成RequestTemplate模板 根据RequestTemplate模板生成Http请求的Request对象 Request对象交给Client去处理,其中Client的网络请求框架可以是HttpURLConnection、HttpClient、OKHttp 最后client封装成LoadBaLanceClient,结合ribbon负载均衡地发起调用

在这里插入图片描述

1.1.5 负载均衡策略

主要有七种;

RoundRobinRule:轮询策略,按照服务顺序依次循环调用 WeightedResponseTimeRule:权重比策略,优先选择权重比高的服务,也就是服务响应时间比较短的,响应时间越长权重比越低 RandomRule:随机策略,服务提供者列表随机选择一个服务 BestAvailableRule:最小连接数策略,获取服务列表中连接数最小的服务实例 RetryRule:重试策略,重试获取已经失效的服务,指定时间没有获取到返回NULL AvailabilityFilteringRule:可用性敏感策略,过滤非健康服务实例,选择lianji ZoneAvoidanceRule:区域敏感策略 1.1.6 饥饿加载

Ribbon-eager-load(饥饿加载)模式 Ribbon 对于负载 Client 是在服务启动后,发生调用的时候才会去创建 Client,所以在第一次发生 http 请求调用的时候,不光要算上 http 的请求时间,还要算上 Client 创建时间,所以第一次调用的时候才会很慢 开启Ribbon饥饿加载

ribbon: nacos: enabled: true # 开启naocos轮询 eager-load: enabled: true # 开启Ribbon的饥饿加载模式(防止第一次请求超时的问题) clients: Lxlxxx-system2 # 指定需要开启的服务(需要开启Ribbon的饥饿加载模式) ReadTimeout: 10000 ConnectTimeout: 10000 MaxAutoRetries: 0 MaxAutoRetriesNextServer: 1 OkToRetryOnAllOperations: false

其实这种饥饿加载模式,类似于客户端负载预热的一个操作,项目启动的时候进行加载,防止服务之间调用可以因为数据量、业务逻辑处理复杂性导致接口超时

1.2 OpenFeign准备工作 1.2.1 引入依赖

在Spring Cloud项目中引入Feign依赖,但是因为feign底层是使用了ribbon作为负载均衡的客户端,而ribbon的负载均衡也是依赖于eureka获得各个服务的地址,所以要引入eureka-client

org.springframework.cloud spring-cloud-starter-openfeign 随便写的版本号 2.0.2.RELEASE org.springframework.cloud spring-cloud-starter-netflix-eureka-client 随便写的版本号 2.0.2.RELEASE 1.2.2 启动类和yml文件

需要在启动类上添加注解@EnableFeignClients以及@EnableDiscoveryClient

@EnableFeignClients @EnableDiscoveryClient @SpringBootApplication public class ProductApplication { public static void main(String[] args) { SpringApplication.run(ProductApplication.class, args); } } server: port: 8082 #配置eureka eureka: client: service-url: defaultZone: http://localhost:8761/eureka instance: status-page-url-path: /info health-check-url-path: /health #服务名称 spring: application: name: product profiles: active: ${boot.profile:dev} #feign的配置,连接超时及读取超时配置 feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic

注意:如果在@EnableFeignClients注解中不指定basePackages属性,Feign会扫描当前包及其子包下的所有接口,如果接口不在这些包中,则会报错。因此,为了避免这种情况,需要在@EnableFeignClients注解中指定basePackages属性,以明确指定需要扫描的包

1.3 使用OpenFeign 1.3.1 简单使用@FeignClient @FeignClient(value = "CART",fallback=Hysitx.class,configuration = FeignConfiguration.class) public interface CartFeignClient { //@PostMapping是调用 目标服务的controller的方法,和对应controller路径保持一致 @PostMapping("/cart/{productId}") Long addCart(@PathVariable("productId")Long productId); @GetMapping(value = "/payment/selectPaymentList") CommonResult selectPaymentList(@RequestParam int pageIndex, @RequestParam int pageSize); @GetMapping(value = "/payment/selectPaymentListByQuery") CommonResult selectPaymentListByQuery(@SpringQueryMap Payment payment); @PostMapping(value = "/payment/create", consumes = "application/json") CommonResult create(@RequestBody Payment payment); @GetMapping("/payment/getPaymentById/{id}") CommonResult getPaymentById(@PathVariable("id") String id); }

@SpringQueryMap: spring cloud项目使用feign的时候都会发现一个问题,就是get方式无法解析对象参数。其实feign是支持对象传递的,但是得是Map形式,而且不能为空,与spring在机制上不兼容,因此无法使用。spring cloud在2.1.x版本中提供了@SpringQueryMap注解,可以传递对象参数,框架自动解析。

编写熔断类,发生错误时回调

import java.util.List; import org.springframework.stereotype.Component; @Component public class Hysitx implements IRemoteCallService{ @Override public List test(String[] names) { System.out.println("接口调用失败"); return null; } }

引入FeignAutoConfiguration配置

@Import(FeignAutoConfiguration.class) @Configuration public class FeignConfiguration{ ... }

上面是最简单的feign client的使用,声明完为feign client后,其他spring管理的类,如service就可以直接注入使用了,例如:

//这里直接注入feign client @Autowired private CartFeignClient cartFeignClient; @PostMapping("/toCart/{productId}") public ResponseEntity addCart(@PathVariable("productId") Long productId){ Long result = cartFeignClient.addCart(productId); return ResponseEntity.ok(result); } 1.3.2 @RequestLine

Feign为什么用的是@RequestLine

这和open-feign的Contract设计有关系,Contract是一个注解解析接口,它决定了接口可以使用什么注解转换到http请求。open-feign在使用@FeignClient的情况下,使用的是SpringMvcContract,它使得被@FeignClient修饰的接口,可以使用@GetMapping,@PostMapping等Spring Mvc注解。 如果我们要使用@RequestLine,则需要替换open-Feign的MVC解析器 // 在feign上写上配置 @FeignClient(name = "test-center", configuration = TestFeignConfig .class) @Configuration// 配置类 public class TestFeignConfig { @Bean public Contract feignContract() { // 配置feign的注释解析器为feign默认解析器而不是mvc解析器 return new feign.Contract.Default(); } }

如果我们不单独配置,则会使用FeignClientsConfiguration中默认配置的SpringMvcContract

@Bean @ConditionalOnMissingBean public Contract feignContract(ConversionService feignConversionService) { return new SpringMvcContract(this.parameterProcessors, feignConversionService); }

@RequestLine与其它请求不同,只需要简单写请求方式和路径就能达到请求其它服务的目的

@FeignClient(value = "feign-server",configuration = FeignConfig.class) //需要一个配置文件 public interface TestService { @RequestLine("POST /feign/test") //对应请求方式和路径 String feign(@RequestBody UserDO userDO); }

配置文件

@EnableFeignClients @SpringBootConfiguration public class FeignConfig { @Bean public Contract contract(){ return new feign.Contract.Default(); } }

@SpringBootConfiguration

标注这个类是一个配置类; 它只是@Configuration注解的派生注解; 它与@Configuration注解的功能一致; 只不过@SpringBootConfiguration是springboot的注解,而@Configuration是spring的注解 1.4 OpenFeign添加header信息

在微服务间使用Feign进行远程调用时需要在header中添加信息,那么 springcloud open feign如何设置 header 呢?有5种方式可以设置请求头信息:

在@RequestMapping注解里添加headers属性 在方法参数前面添加@RequestHeader注解 在方法或者类上添加@Headers的注解 在方法参数前面添加@HeaderMap注解 实现RequestInterceptor接口 1.4.1 在@RequestMapping注解里添加headers属性

在application.yml中配置

app.secret: appSecretVal @PostMapping(value = "/book/api", headers = {"Content-Type=application/json;", "App-Secret=${app.secret}"}) void saveBook(@RequestBody BookDto condition); 1.4.2 在方法参数前面添加@RequestHeader注解

设置单个header属性

@GetMapping(value = "/getStuDetail") public StudentVo getStudentDetail(@RequestBody StudentDto condition, @RequestHeader("Authorization") String token);

设置多个header属性

@PostMapping(value = "/card") public CardVo createCard(@RequestBody CardDto condition, @RequestHeader MultiValueMap headers); 1.4.3 在方法或者类上添加@Headers的注解

使用feign自带契约

@Configuration public class FooConfiguration { @Bean public Contract feignContract() { return new feign.Contract.Default(); } }

FeignClient使用@RequestLine注解, 而未配置feign自带契约Contract时, @Headers不会起作用, 而且启动项目会报错:

Method xxx not annotated with HTTP method type (ex. GET, POST) @RequestLine is a core Feign annotation, but you are using the Spring Cloud @FeignClientwhich uses Spring MVC annotations.

配置@Headers注解

@FeignClient(url = "${user.api.url}", name = "user", configuration = FooConfiguration.class) public interface UserFeignClient { @RequestLine("GET /simple/{id}") @Headers({"Content-Type: application/json;charset=UTF-8", "Authorization: {token}"}) public User findById(@Param("id") String id, @Param("token") String token); }

使用@Param可以动态配置Header属性 网上很多在说 @Headers不起作用,其实@Headers注解没有生效的原因是:官方的Contract没有生效导致的

1.4.4 在方法参数前面添加@HeaderMap注解

使用feign自带契约

配置@HeaderMap注解

@FeignClient(url = "${user.api.url}", name = "user", configuration = FooConfiguration.class) public interface UserFeignClient { @RequestLine("GET /simple/{id}") public User findById(@Param("id") String id, @HeaderMap HttpHeaders headers); } 1.4.5 实现RequestInterceptor接口

值得注意的一点是: 如果FeignRequestInterceptor注入到spring容器的话就会全局生效, 就是说即使在没有指定configuration属性的FeignClient该配置也会生效 配置@Component或@Service或@Configuration就可以将该配置注入spring容器中, 即可实现全局配置, 从而该项目中的所有FeignClient的feign接口都可以使用该配置. 如果只想给指定FeignClient的feign接口使用该配置, 请勿将该类配置注入spring中.

@Configuration public class FeignRequestInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { template.header(HttpHeaders.AUTHORIZATION, "tokenVal"); } } 1.5 OpenFeign日志打印

Feign 提供了日志打印功能,我们可以通过配置来调整日志级别,从而了解 Feign 中 Http 请求的细节。 说白了就是对Feign接口的调用情况进行监控和输出

日志级别:

NONE:默认的,不显示任何日志; BASIC:仅记录请求方法、URL、响应状态码及执行时间; HEADERS:除了 BASIC 中定义的信息之外,还有请求和响应的头信息; FULL:除了 HEADERS 中定义的信息之外,还有请求和响应的正文及元数据。

配置日志Bean:

import feign.Logger; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignConfig { @Bean Logger.Level feignLoggerLevel() { return Logger.Level.FULL; } }

YML文件里需要开启日志的Feign客户端

logging: level: # feign日志以什么级别监控哪个接口 com.gzl.cn.service.PaymentFeignService: debug

后台日志查看: 在这里插入图片描述

1.6 手动创建 Feign 客户端

@FeignClient无法支持同一service具有多种不同配置的FeignClient,因此,在必要时需要手动build FeignClient。

@FeignClient(value = “CLOUD-PAYMENT-SERVICE”) 以这个为例,假如出现两个服务名称为CLOUD-PAYMENT-SERVICE的FeignClient,项目直接会启动报错,但是有时候我们服务之间调用的地方较多,不可能将所有调用都放到一个FeignClient下,这时候就需要自定义来解决这个问题!

官网当中也明确提供了自定义FeignClient,以下是在官网基础上对自定义FeignClient的一个简单封装

首先创建FeignClientConfigurer类,这个类相当于build FeignClient的工具类

import feign.*; import feign.codec.Decoder; import feign.codec.Encoder; import feign.slf4j.Slf4jLogger; import org.springframework.cloud.openfeign.FeignClientsConfiguration; import org.springframework.context.annotation.Import; @Import(FeignClientsConfiguration.class) public class FeignClientConfigurer { private Decoder decoder; private Encoder encoder; private Client client; private Contract contract; public FeignClientConfigurer(Decoder decoder, Encoder encoder, Client client, Contract contract) { this.decoder = decoder; this.encoder = encoder; this.client = client; this.contract = contract; } public RequestInterceptor getUserFeignClientInterceptor() { return new RequestInterceptor() { @Override public void apply(RequestTemplate requestTemplate) { // 添加header } }; } public T buildAuthorizedUserFeignClient(Class clazz, String serviceName) { return getBasicBuilder().requestInterceptor(getUserFeignClientInterceptor()) //默认是Logger.NoOpLogger .logger(new Slf4jLogger(clazz)) //默认是Logger.Level.NONE(一旦手动创建FeignClient,全局配置的logger就不管用了,需要在这指定) .logLevel(Logger.Level.FULL) .target(clazz, buildServiceUrl(serviceName)); } private String buildServiceUrl(String serviceName) { return "http://" + serviceName; } protected Feign.Builder getBasicBuilder() { return Feign.builder().client(client).encoder(encoder).decoder(decoder).contract(contract); } }

使用工具类的方法创建多个FeignClient配置

import com.gzl.cn.service.FeignTest1Service; import feign.Client; import feign.Contract; import feign.codec.Decoder; import feign.codec.Encoder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @Configuration public class FeignClientConfiguration extends FeignClientConfigurer { public FeignClientConfiguration(Decoder decoder, Encoder encoder, Client client, Contract contract) { super(decoder, encoder, client, contract); } @Bean public FeignTest1Service feignTest1Service() { return super.buildAuthorizedUserFeignClient(FeignTest1Service.class, "CLOUD-PAYMENT-SERVICE"); } // 假如多个FeignClient在这里定义即可 }

其中,super.buildAuthorizedUserFeignClient()方法中,第一个参数为调用别的服务的接口类,第二个参数为被调用服务在注册中心的service-id。

public interface FeignTest1Service { @GetMapping(value = "/payment/get/{id}") CommonResult getPaymentById(@PathVariable("id") Long id); }

使用的时候正常注入使用即可

@Resource private FeignTest1Service feignTest1Service; 1.7 Feign配置

Feign底层默认是使用jdk中的HttpURLConnection发送HTTP请求(默认不支持线程池,若请求多了会响应超时),feign也提供了OKhttp来发送请求 feign.client.*:支持按实例进行配置,feign.httpclient.*:全局共享一套配置,包含线程池配置,但只影响HttpClient和OkHttp,不影响HttpURLConnection

所谓按实例进行配置,就是指每个FeignClient实例都可以通过feign.client..*来单独进行配置,注意首字母小写。而 feign.client.default.* 表示默认配置

1.7.1 使用OKhttp

具体配置如下

feign: client: config: default: connectTimeout: 5000 readTimeout: 5000 loggerLevel: basic okhttp: enabled: true hystrix: enabled: true 1.7.2 开启GZIP压缩

Spring Cloud Feign支持对请求和响应进行GZIP压缩,以提高通信效率。 application.yml配置信息如下:

feign: compression: request: #请求 enabled: true #开启 mime-types: text/xml,application/xml,application/json #开启支持压缩的MIME TYPE min-request-size: 2048 #配置压缩数据大小的下限 response: #响应 enabled: true #开启响应GZIP压缩

注意: 由于开启GZIP压缩之后,Feign之间的调用数据通过二进制协议进行传输,返回值需要修改为ResponseEntity才可以正常显示,否则会导致服务之间的调用乱码。 示例如下:

@PostMapping("/order/{productId}") ResponseEntity addCart(@PathVariable("productId") Long productId); 2 Feign拦截器RequestInterceptor 2.1 定义

在使用feign做服务间调用的时候,如何修改请求的头部或编码信息呢,可以通过实现RequestInterceptor接口的apply方法,feign在发送请求之前都会调用该接口的apply方法,所以我们也可以通过实现该接口来记录请求发出去的时间点

2.2 RequestInterceptor public interface RequestInterceptor { /** * Called for every request. Add data using methods on the supplied {@link RequestTemplate}. */ void apply(RequestTemplate template); }

RequestInterceptor接口定义了apply方法,其参数为RequestTemplate;它有一个抽象类为BaseRequestInterceptor,还有几个实现类分别为BasicAuthRequestInterceptor、FeignAcceptGzipEncodingInterceptor、FeignContentGzipEncodingInterceptor

BasicAuthRequestInterceptor : 实现了RequestInterceptor接口,其apply方法往RequestTemplate添加名为Authorization的header BaseRequestInterceptor : 定义了addHeader方法,往requestTemplate添加非重名的header FeignAcceptGzipEncodingInterceptor : 继承了BaseRequestInterceptor,它的apply方法往RequestTemplate添加了名为Accept-Encoding,值为gzip,deflate的header FeignContentGzipEncodingInterceptor : 继承了BaseRequestInterceptor,其apply方法先判断是否需要compression,即mimeType是否符合要求以及content大小是否超出阈值,需要compress的话则添加名为Content-Encoding,值为gzip,deflate的header 2.3 配置RequestInterceptor

可以通过配置类和配置文件两种方式注册RequestInterceptor

通过配置类配置时,通过FeignContext获取RequestInterceptor bean; 通过配置文件注册时,通过ApplicationContext获取RequestInterceptor bean

通过配置类进行配置 设置配置类有两种方式

2.3.1 通过@EnableFeignClients的defaultConfiguration属性配置

@EnableFeignClients注解的 defaultConfiguration 属性值为 @Configuration 注解的配置类,配置类内定义的 bean 会注册为所有 @FeignClient 的默认配置。 配置信息会保存为FeignClientSpecification对象。

2.3.2 通过@FeignClient的configuration属性配置

@FeignClient 注解的 configuration 属性值为 @Configuration 注解的配置类,其定义的 bean 为每个FeignClient 的专有 bean

FeignContext 为每一个 @FeignClient 注解的接口提供独立的AnnotationConfigApplicationContext,其中包含 FeignClient 的私有bean 和 EnableFeignClient 的默认 bean。

2.3.3 通过配置文件配置 @Component public class DpAuthFeignReqInterceptor implements RequestInterceptor { @Override public void apply(RequestTemplate template) { ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); if (attributes != null) { HttpServletRequest request = attributes.getRequest(); String jwtToken = request.getHeader(JWTTokenConstant.TOKEN_NAME); //传递token 给下游 template.header(JWTTokenConstant.TOKEN_NAME, jwtToken); } } }


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3